package de.herzog.bbp.view.javafx;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import javafx.event.EventHandler;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AutoCompleteHandler<T> implements EventHandler<KeyEvent> {

	private static final Logger LOG = LoggerFactory.getLogger(AutoCompleteHandler.class);

	private static final Set<KeyCode> KEY_CODES_TO_IGNORE = new TreeSet<>(Arrays.asList(
	//
			KeyCode.BACK_SPACE, KeyCode.DELETE, KeyCode.HOME, KeyCode.END));

	/** The text field on which to auto complete */
	private TextField textField;
	/** The list of items to use as auto complete candidates */
	private List<String> autoCompleteList;

	/**
	 * The list of items on the auto complete list that match the beginning of
	 * the current text in the field
	 */
	private List<String> matchList;
	/** The index of the item on the match list that is currently used as match */
	private int matchIndex;
	/**
	 * The start index of the text in the field that the user has not typed but
	 * was filled by auto complete
	 */
	private int matchStart;

	public AutoCompleteHandler(TextField textField, List<String> autoCompleteList) {
		super();
		this.textField = textField;
		this.autoCompleteList = autoCompleteList;
		LOG.debug("Autocompletion: " + autoCompleteList);
		resetMatches();
		textField.selectRange(0, textField.getText().length());
		autoComplete();
	}

	@Override
	public void handle(KeyEvent keyEvent) {
		KeyCode keyCode = keyEvent.getCode();
		if (keyCode == KeyCode.DOWN || keyCode == KeyCode.PAGE_DOWN) {
			autoCompleteNext(1);
		} else if (keyCode == KeyCode.UP || keyCode == KeyCode.PAGE_UP) {
			autoCompleteNext(-1);
		} else if (keyCode == KeyCode.TAB || keyCode == KeyCode.ENTER) {
			// resetMatches();
		} else if (keyCode.isModifierKey()) {
			// ignore
		} else if (KEY_CODES_TO_IGNORE.contains(keyCode)) {
			// ignore
		} else {
			autoComplete();
		}
	}

	public void resetMatches() {
		matchList = new ArrayList<String>();
		matchIndex = -1;
		matchStart = -1;
	}

	public void autoComplete() {
		boolean isEmptyText = (textField.getText().length() == 0);
		if (!isEmptyText) {
			int selectionStart = textField.getSelection().getStart();
			String textBeforeSelection = textField.getText().substring(0, selectionStart);
			LOG.trace("Searching for " + textBeforeSelection + " in " + autoCompleteList);
			matchList = new ArrayList<String>();
			int useMatchIndex = 0;
			for (String text : autoCompleteList) {
				if (text.toUpperCase().startsWith(textBeforeSelection.toUpperCase())) {
					matchList.add(text);
					if (text.equalsIgnoreCase(textField.getText())) {
						// This match was an exact match, use it as the
						// resulting match
						useMatchIndex = matchList.size() - 1;
					}
				}
			}
			if (matchList.size() >= 0) {
				LOG.trace("MatchList=" + matchList.toString());
				LOG.trace("UseMatchIndex=" + useMatchIndex);
				useMatch(useMatchIndex, selectionStart);
			} else {
				LOG.trace("No match found");
			}
		}
	}

	private void autoCompleteNext(int direction) {
		if (matchList.size() > 0) {
			int nextMatchIndex = matchIndex + direction;
			if (nextMatchIndex < 0 || nextMatchIndex >= matchList.size()) {
				nextMatchIndex = matchIndex;
			}
			useMatch(nextMatchIndex, matchStart);
		}
	}

	private void useMatch(int matchIndex, int matchStart) {
		this.matchIndex = matchIndex;
		this.matchStart = matchStart;
		if (matchIndex < matchList.size()) {
			String textMatch = matchList.get(matchIndex);
			textField.setText(textMatch);
			textField.selectRange(matchStart, textMatch.length());
		} else {
			// Typed prefix does not match any item in the autocomplete list
		}
	}
}
